package service

import (
	"errors"
	"fmt"
	"gitee.com/sdumeeting/sfu-server/global"
	"gitee.com/sdumeeting/sfu-server/internal/model"
	"gitee.com/sdumeeting/sfu-server/pkg/util"
	"github.com/gorilla/websocket"
	"github.com/pion/webrtc/v2"
	"net/http"
)

const (
	MethodJoin      = "join"
	MethodPublish   = "publish"
	MethodSubscribe = "subscribe"
	MethodChat      = "chat"

	ResponseNewUser          = "newUser"
	ResponseJoinSuccess      = "joinSuccess"
	ResponsePublishSuccess   = "publishSuccess"
	ResponseSubscribeSuccess = "subscribeSuccess"
	ResponseUserLeave        = "userLeave"
	ResponseCatSuccess       = "chatSuccess"
)

type WebSocketHandler struct{}

func (handler *WebSocketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 实例化 Upgrader 对象，并设置其中的 CheckOrigin 属性，以解决跨域问题
	upgrader := websocket.Upgrader{
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}

	// 将 HTTP 服务连接升级为 WebSocket 协议
	socket, err := upgrader.Upgrade(w, r, http.Header{})
	if err != nil {
		global.Logger.Panicf("server.upgrader.Upgrade err: %v", err)
	}

	// 将 WebSocket 进行初始化
	conn := model.NewConnection(socket)

	// 添加一个监听器
	// 该有 message 事件发出时，会执行以下回调函数
	conn.On("message", func(message []byte) {
		// 对消息进行解析
		var request map[string]interface{}
		request, err = util.Unmarshal(string(message))
		if err != nil {
			return
		}

		var ok bool
		var data map[string]interface{}
		var messageType string

		// 判断 type 的有效性
		if messageType, ok = request["type"].(string); !ok {
			return
		}

		// 判断 data 的有效性
		if data, ok = request["data"].(map[string]interface{}); !ok {
			return
		}

		switch messageType {
		case MethodJoin:
			err = handleJoinRequest(data, conn)
		case MethodPublish:
			err = handlePublishRequest(data)
		case MethodSubscribe:
			err = handleSubscribeRequest(data)
		case MethodChat:
			err = handleChatRequest(data)
		default:
			err = errors.New("unknown request")
		}

		if err != nil {
			_ = conn.Send(util.Marshal(map[string]interface{}{
				"type": "requestError",
				"data": err.Error(),
			}))
		}
	})

	conn.On("close", func() {
		userId, meetingId := -1, -1
		for _, meeting := range meetingList.GetMeetings() {
			for _, user := range meeting.GetUsers() {
				if user.Conn == conn {
					userId = user.GetId()
					meetingId = meeting.GetId()
					break
				}
			}
		}

		if meetingId == -1 {
			global.Logger.Error("model and user are not found")
			return
		}

		handleLeave(meetingId, userId)
	})

	conn.Receive()
}

func handleJoinRequest(request map[string]interface{}, conn *model.Connection) error {
	var meetingId, userId int
	var meetingIdF, userIdF float64
	var userName string
	var ok bool

	// 判断 meetingId 的有效性
	if meetingIdF, ok = request["meetingId"].(float64); !ok {
		return errors.New("parameter \"meetingId\" must be integer")
	}
	meetingId = int(meetingIdF)

	// 判断 userId 的有效性
	if userIdF, ok = request["userId"].(float64); !ok {
		return errors.New("parameter \"userId\" must be integer")
	}
	userId = int(userIdF)

	// 判断 userName 的有效性
	if userName, ok = request["userName"].(string); !ok {
		return errors.New("parameter \"userName\" must be string")
	}

	// 从会议列表中获取该会议
	meeting := meetingList.GetMeetingById(meetingId)

	// 如果该会议不存在，则报错
	if meeting == nil {
		return errors.New("meeting does not exist")
	}

	// 从会议内的用户列表中获取该用户
	user := meeting.GetUserById(userId)

	// 若该用户已经在会议，则向用户提示
	if user != nil {
		return errors.New("user is already in the meeting")
	}

	// 创建该用户
	user = model.NewUser(userId, userName, conn)
	meeting.AddUser(user)

	// 向其他用户告知有新成员加入会议
	response := make(map[string]interface{})
	response["meetingId"] = meeting.GetId()
	response["newUserInfo"] = user.GetUserInfo()
	meeting.SendToOthers(user, ResponseNewUser, response)

	// 向该用户发送会议内的其他成员以及发布者的信息
	response["allUserInfos"] = meeting.GetAllUserInfos()
	response["pubIds"] = meeting.GetAllPublisherIds()
	user.SendMessage(ResponseJoinSuccess, response)

	return nil
}

func handlePublishRequest(request map[string]interface{}) error {
	var meetingId, userId int
	var meetingIdF, userIdF float64
	var jsep map[string]interface{}
	var sdp string
	var ok bool

	// 判断 userId 的有效性
	if userIdF, ok = request["userId"].(float64); !ok {
		return errors.New("parameter \"userId\" must be integer")
	}
	userId = int(userIdF)

	// 判断 meetingId 的有效性
	if meetingIdF, ok = request["meetingId"].(float64); !ok {
		return errors.New("parameter \"meetingId\" must be integer")
	}
	meetingId = int(meetingIdF)

	// 判断 jsep 的有效性
	if jsep, ok = request["jsep"].(map[string]interface{}); !ok {
		return errors.New("parameter \"jsep\" must be map[string]interface{}")
	}

	// 判断 SDP 的有效性
	if sdp, ok = jsep["sdp"].(string); !ok {
		return errors.New("parameter \"sdp\" must be string")
	}

	// 从会议列表中获取该会议
	meeting := meetingList.GetMeetingById(meetingId)
	if meeting == nil {
		return errors.New("meeting is not found")
	}

	// 从会议内的用户列表中获取该用户
	user := meeting.GetUserById(userId)
	if user == nil {
		return errors.New("user is not found")
	}

	// 根据参数生成对方的 SDP 信息
	offer := webrtc.SessionDescription{
		Type: webrtc.SDPTypeOffer,
		SDP:  sdp,
	}

	// 将该用户添加到发布者列表中
	meeting.AddPublisherById(userId)

	// 生成本地 SDP
	answer, err := meeting.AnswerPublisher(userId, offer)
	if err != nil {
		fmt.Println(err)
		return errors.New("fail to answer publisher")
	}

	// 向所有用户发送新发布者的 id
	response := make(map[string]interface{})
	response["meetingId"] = meetingId
	response["pubId"] = userId
	response["jsep"] = answer
	meeting.Broadcast(ResponsePublishSuccess, response)

	return nil
}

func handleSubscribeRequest(request map[string]interface{}) error {
	var meetingId, userId, pubId int
	var meetingIdF, userIdF, pubIdF float64
	var jsep map[string]interface{}
	var sdp string
	var ok bool

	// 判断 userId 的有效性
	if userIdF, ok = request["userId"].(float64); !ok {
		return errors.New("parameter \"userId\" must be integer")
	}
	userId = int(userIdF)

	// 判断 meetingId 的有效性
	if meetingIdF, ok = request["meetingId"].(float64); !ok {
		return errors.New("parameter \"meetingId\" must be integer")
	}
	meetingId = int(meetingIdF)

	// 判断 pubId 的有效性
	if pubIdF, ok = request["pubId"].(float64); !ok {
		return errors.New("parameter \"pubId\" must be integer")
	}
	pubId = int(pubIdF)

	// 判断 jsep 的有效性
	if jsep, ok = request["jsep"].(map[string]interface{}); !ok {
		return errors.New("parameter \"jsep\" must be map[string]interface{}")
	}

	// 判断 SDP 的有效性
	if sdp, ok = jsep["sdp"].(string); !ok {
		return errors.New("parameter \"sdp\" must be string")
	}

	// 从会议列表中获取该会议
	meeting := meetingList.GetMeetingById(meetingId)
	if meeting == nil {
		return errors.New("meeting is not found")
	}

	// 从会议内的用户列表中获取该用户
	user := meeting.GetUserById(userId)
	if user == nil {
		return errors.New("user is not found")
	}

	// 根据参数生成对方的 SDP 信息
	offer := webrtc.SessionDescription{
		Type: webrtc.SDPTypeOffer,
		SDP:  sdp,
	}

	// 将用户添加到订阅者列表中
	meeting.AddSubscriberById(userId)

	// 生成本地 SDP
	answer, err := meeting.AnswerSubscriber(userId, pubId, offer)
	if err != nil {
		fmt.Println(err)
		return errors.New("fail to answer subscriber")
	}

	// 向该用户发送响应信息
	response := make(map[string]interface{})
	response["meetingId"] = meetingId
	response["pubId"] = pubId
	response["jsep"] = answer
	user.SendMessage(ResponseSubscribeSuccess, response)

	meeting.SendPLI(userId)

	return nil
}

// 处理聊天请求
func handleChatRequest(request map[string]interface{}) error {
	var meetingId, userId int
	var meetingIdF, userIdF float64
	var ok bool

	// 判断 userId 的有效性
	if userIdF, ok = request["userId"].(float64); !ok {
		return errors.New("parameter \"userId\" must be integer")
	}
	userId = int(userIdF)

	// 判断 meetingId 的有效性
	if meetingIdF, ok = request["meetingId"].(float64); !ok {
		return errors.New("parameter \"meetingId\" must be integer")
	}
	meetingId = int(meetingIdF)

	// 从会议列表中获取该会议
	meeting := meetingList.GetMeetingById(meetingId)
	if meeting == nil {
		return errors.New("meeting is not found")
	}

	// 检查用户是否在该会议中
	user := meeting.GetUserById(userId)
	if user == nil {
		return errors.New("user is not found")
	}

	// 向房间内的所有用户广播消息
	response := make(map[string]interface{})
	response["meetingId"] = meetingId
	response["userId"] = userId
	response["message"] = request["message"]
	meeting.Broadcast(ResponseCatSuccess, response)

	return nil
}

func handleLeave(meetingId, userId int) {
	meeting := meetingList.GetMeetingById(meetingId)
	if meeting == nil {
		return
	}

	user := meeting.GetUserById(userId)
	if user == nil {
		return
	}

	// 将该用户离开的消息发送给会议内的其他用户
	response := make(map[string]interface{})
	response["leaverId"] = userId
	meeting.SendToOthers(user, ResponseUserLeave, response)

	// 屏幕共享用户强制离开时，把会议中的标记关闭
	if userId < 0 {
		err := meeting.EndScreenSharing()
		if err != nil {
			fmt.Println(err)
		}
	}

	// 将该用户移除
	meeting.DeletePublisherById(userId)
	meeting.DeleteSubscriberById(userId)
	meeting.DeleteUserById(userId)
	user.Close()
}
